﻿using System;
using System.Collections.Generic;
using System.Linq;
using ToyStore.Domain.Contracts.Tasks;
using ToyStore.Domain.Store;
using ToyStore.Domain.System;
using ToyStore.Infrastructure;

namespace ToyStore.Tasks
{
	/// <summary>
	/// Logika biznesowa koszyka z zakupami.
	/// </summary>
	public class CartTask : ICartTask
	{
		private readonly IExRepository<Cart> _cartRepository;
		private readonly IExRepository<Product> _productRepository;
		private readonly IExRepository<CartItem> _cartItemRepository;
		private readonly IExRepository<Order> _orderRepository;
		private readonly IExRepository<OrderedProduct> _orderedProductRepository;

		/// <summary>
		/// Nazwa zmiennej przechowującej Guid koszyka w sesji użytkownika.
		/// </summary>
		public static string CartSessionGuid = "CartSessionGuid";

		/// <summary>
		/// Konstruktor.
		/// </summary>
		/// <param name="cartRepository"></param>
		/// <param name="productRepository"></param>
		/// <param name="cartItemRepository"></param>
		/// <param name="orderRepository"></param>
		/// <param name="orderedProductRepository"></param>
		public CartTask(IExRepository<Cart> cartRepository, IExRepository<Product> productRepository, IExRepository<CartItem> cartItemRepository, IExRepository<Order> orderRepository, IExRepository<OrderedProduct> orderedProductRepository)
		{
			_cartRepository = cartRepository;
			_productRepository = productRepository;
			_cartItemRepository = cartItemRepository;
			_orderRepository = orderRepository;
			_orderedProductRepository = orderedProductRepository;
		}

		/// <summary>
		/// Stworzenie nowego koszyka.
		/// Po stworzeniu koszyka należy zapamiętać jego Guid w sesji użytkownika.
		/// </summary>
		/// <returns>Koszyk wraz z wygenerowanym Guid'em.</returns>
		public Cart CreateCart()
		{
			return CreateCart(null);
		}

		/// <summary>
		/// Stworzenie nowego koszyka.
		/// Po stworzeniu koszyka należy zapamiętać jego Guid w sesji użytkownika.
		/// </summary>
		/// <returns>Koszyk wraz z wygenerowanym Guid'em.</returns>
		public Cart CreateCart(User user)
		{
			string guid = string.Empty;
			for (int i = 0; i < 10; i++)
			{
				guid = Guid.NewGuid().ToString();
				if (GetCartByGuid(guid) == null) break;

				// Tutaj to raczej niemozliwe zeby doszło ;-)
				if (i == 9)
				{
					throw new Exception("Nie udało się stworzyć koszyka o unikalnym Guid");
				}
			}

			// Tworzymy koszyk o wygenerowanym nuerze Guid.
			var cart = new Cart
							{
								CartGuid = guid,
								User = user
							};

			return _cartRepository.SaveOrUpdate(cart);
		}

		/// <summary>
		/// Pobranie koszyka po jego numerze Guid (przechowywany on jest w sesji użytkownika).
		/// </summary>
		/// <param name="guid">Guid pobrany z sesji.</param>
		/// <returns>Koszyk użytkownika</returns>
		public Cart GetCartByGuid(string guid)
		{
			return _cartRepository.GetQuery().FirstOrDefault(c => c.CartGuid == guid);
		}

		/// <summary>
		/// Dodanie do koszyka produktu.
		/// </summary>
		/// <param name="cart">Koszyk, do którego dodajemy.</param>
		/// <param name="productId">Numer Id produktu.</param>
		/// <returns>Koszyk z uzupełnionymi danymi.</returns>
		public Cart AddProductToCart(Cart cart, int productId)
		{
			Product product = _productRepository.Get(productId);
			return AddProductToCart(cart, product);
		}

		/// <summary>
		/// Dodanie do koszyka produktu.
		/// </summary>
		/// <param name="cart">Koszyk, do którego dodajemy.</param>
		/// <param name="product">Produkt do dodania.</param>
		/// <returns>Koszyk z uzupełnionymi danymi.</returns>
		public Cart AddProductToCart(Cart cart, Product product)
		{
			if (cart == null) throw new ArgumentNullException("cart");
			if (product == null) throw new ArgumentNullException("product");

			// Sprawdzamy czy produkt jest juz w koszyku.
			CartItem cartItem = cart.CartItems.FirstOrDefault(c => c.Product.Id == product.Id && c.IsDeleted == false);

			if (cartItem == null)
			{
				// Jesli nie istnieje produkt w koszyku, wowczas go dodajemy.
				CartItem newCartItem = new CartItem
										{
											Amount = 1,
											Cart = cart,
											Product = product
										};
				cart.CartItems.Add(newCartItem);

				_cartRepository.SaveOrUpdate(cart);
				_cartItemRepository.SaveOrUpdate(newCartItem);
			}

			// Na chwilę obecną nie umożliwiamy kupowania kilku ilości danego produktu.
			// Prawdopodobnie nigdy takie coś potrzebne nie będzie.
			/*
			else
			{
				cartItem.Amount++;

				_cartItemRepository.SaveOrUpdate(cartItem);
			}
			*/
			return RefreshCart(cart);
		}

		/// <summary>
		/// Usunięcie produktu z koszyka.
		/// </summary>
		/// <param name="cart">Koszyk, z którego usuwamy.</param>
		/// <param name="productId">Numer Id produktu do usunięcia.</param>
		/// <returns>Koszyk z uzupełnionymi danymi.</returns>
		public Cart RemoveProductFromCart(Cart cart, int productId)
		{
			Product product = _productRepository.Get(productId);
			return RemoveProductFromCart(cart, product);
		}

		/// <summary>
		/// Usunięcie produktu z koszyka.
		/// </summary>
		/// <param name="cart">Koszyk, z którego usuwamy.</param>
		/// <param name="product">Produkt do usunięcia.</param>
		/// <returns>Koszyk z uzupełnionymi danymi.</returns>
		public Cart RemoveProductFromCart(Cart cart, Product product)
		{
			if (cart == null) throw new ArgumentNullException("cart");
			if (product == null) throw new ArgumentNullException("product");

			CartItem cartItem = cart.CartItems.FirstOrDefault(c => c.Product.Id == product.Id && c.IsDeleted == false);
			if (cartItem != null)
			{
				_cartItemRepository.Delete(cartItem);
			}

			return RefreshCart(cart);
		}

		/// <summary>
		/// Ustawienie ilości produktów w koszyku.
		/// </summary>
		/// <param name="cart">Koszyk.</param>
		/// <param name="productId">Numer Id produktu.</param>
		/// <param name="amount">Ilość do ustawienia.</param>
		/// <returns>Koszyk z uzupełnionymi danymi.</returns>
		public Cart SetProductAmountInCart(Cart cart, int productId, int amount)
		{
			Product product = _productRepository.Get(productId);
			return SetProductAmountInCart(cart, product, amount);
		}

		/// <summary>
		/// Ustawienie ilości produktów w koszyku.
		/// </summary>
		/// <param name="cart">Koszyk.</param>
		/// <param name="product">Produkt.</param>
		/// <param name="amount">Ilość do ustawienia.</param>
		/// <returns>Koszyk z uzupełnionymi danymi.</returns>
		public Cart SetProductAmountInCart(Cart cart, Product product, int amount)
		{
			if (cart == null) throw new ArgumentNullException("cart");
			if (product == null) throw new ArgumentNullException("product");

			CartItem cartItem = cart.CartItems.FirstOrDefault(c => c.Product.Id == product.Id && c.IsDeleted == false);

			if (cartItem != null)
			{
				if (amount == 0)
				{
					_cartItemRepository.Delete(cartItem);
				}
				else if (amount > 0)
				{
					cartItem.Amount = amount;

					_cartItemRepository.SaveOrUpdate(cartItem);
				}
			}

			return RefreshCart(cart);
		}

		/// <summary>
		/// Opróżnienie koszyka z wszelkich produktów.
		/// </summary>
		/// <param name="cart">Koszyk.</param>
		/// <returns>Koszyj z uzupełnionymi danymi.</returns>
		public Cart EmptyCart(Cart cart)
		{
			if (cart == null) throw new ArgumentNullException("cart");

			foreach (var item in cart.CartItems.Where(c => c.IsDeleted == false))
			{
				_cartItemRepository.Delete(item);
			}

			return RefreshCart(cart);
		}

		/// <summary>
		/// Stworzenie zamówienia na podstawie koszyka.
		/// </summary>
		/// <param name="cart">Koszyk.</param>
		/// <param name="user">Użytkownik, dokonujący zakupu.</param>
		/// <returns>Zamówienie.</returns>
		public Order CreateOrder(Cart cart, User user)
		{
			if (cart == null) throw new ArgumentNullException("cart");
			if (user == null) throw new ArgumentNullException("user");

			Order order = new Order
							{
								Cart = cart,
								User = user
							};

			// Obiekt, w ktorym przechowywane będą "wartość vat" : "sumaryczna wartość produktów o danym vat (netto)".
			IDictionary<decimal, decimal> prices = new Dictionary<decimal, decimal>();

			foreach (var cartItem in cart.CartItems.Where(c => c.IsDeleted == false))
			{
				// Pobieramy produkt aby miec najaktualniejsze dane o produkcie.
				Product product = _productRepository.Get(cartItem.Product.Id);

				decimal itemPriceNetto = product.PriceNetto * cartItem.Amount;
				OrderedProduct orderedProduct = new OrderedProduct
													{
														Amount = cartItem.Amount,
														Order = order,
														PriceNetto = product.PriceNetto,
														SumPriceNetto = itemPriceNetto,
														Product = product,
														Vat = product.Vat,
														SumPriceBrutto = itemPriceNetto + (itemPriceNetto * (product.Vat.Value / 100.0m))
													};
				product.OrderedProducts.Add(orderedProduct);
				order.OrderedProducts.Add(orderedProduct);

				// Dodawanie do slownika w zaleznosci od wartosci Vat.
				if (prices.ContainsKey(product.Vat.Value))
				{
					decimal sumPriceNetto = prices[product.Vat.Value];
					prices[product.Vat.Value] = sumPriceNetto + orderedProduct.SumPriceNetto;
				}
				else
				{
					prices.Add(product.Vat.Value, orderedProduct.SumPriceNetto);
				}

				_orderedProductRepository.SaveOrUpdate(orderedProduct);
				_productRepository.SaveOrUpdate(product);
				_orderRepository.SaveOrUpdate(order);

				order.SumPriceNetto += orderedProduct.SumPriceNetto;
			}

			// Przeliczanie sumarycznej wartosci brutto produktow.
			order.SumPriceBrutto = 0;
			foreach (KeyValuePair<decimal, decimal> keyValuePair in prices)
			{
				order.SumPriceBrutto += keyValuePair.Value + (keyValuePair.Value * (keyValuePair.Key / 100.0m));
			}

			return _orderRepository.SaveOrUpdate(order);
		}

		/// <summary>
		/// Odświeżenie danych w koszyku (np. przeliczenie wartości sumarycznej na podstawie aktualnych danych w bazie).
		/// </summary>
		/// <param name="cart">Koszyk.</param>
		/// <returns>Koszyk z odświeżonymi danymi.</returns>
		public Cart RefreshCart(Cart cart)
		{
			if (cart == null) throw new ArgumentNullException("cart");

			// Czyscimy ilość.
			cart.Amount = 0;
			// Obiekt, w ktorym przechowywane będą "wartość vat" : "sumaryczna wartość produktów o danym vat (netto)".
			IDictionary<decimal, decimal> prices = new Dictionary<decimal, decimal>();

			foreach (var cartItem in cart.CartItems.Where(c => c.IsDeleted == false))
			{
				Product product = _productRepository.Get(cartItem.Product.Id);

				// Obliczenie ceny netto oraz brutto poszczegółnych produktów.
				cartItem.SumPriceNetto = cartItem.Amount * product.PriceNetto;
				cartItem.SumPriceBrutto = cartItem.SumPriceNetto + (cartItem.SumPriceNetto * (product.Vat.Value / 100.0m));

				// Obliczamy ilość produktów w koszyku.
				cart.Amount += cartItem.Amount;

				// Obliczamy cenę netto całego koszyka.
				cart.SumPriceNetto += cartItem.SumPriceNetto;

				// Dodawanie do slownika w zaleznosci od wartosci Vat.
				if (prices.ContainsKey(product.Vat.Value))
				{
					decimal sumPriceNetto = prices[product.Vat.Value];
					prices[product.Vat.Value] = sumPriceNetto + cartItem.SumPriceNetto;
				}
				else
				{
					prices.Add(product.Vat.Value, cartItem.SumPriceNetto);
				}

				_cartItemRepository.SaveOrUpdate(cartItem);
			}

			// Przeliczanie sumarycznej wartosci brutto produktow.
			cart.SumPriceBrutto = 0;
			foreach (KeyValuePair<decimal, decimal> keyValuePair in prices)
			{
				cart.SumPriceBrutto += keyValuePair.Value + (keyValuePair.Value * (keyValuePair.Key / 100.0m));
			}

			return _cartRepository.SaveOrUpdate(cart);
		}
        /// <summary>
        /// Zwraca liste aktualnie wybranych przedmiotów w koszyku
        /// </summary>
        /// <param name="guid"></param>
        /// <returns></returns>
        public IList<Product> GetProductsInUserCart(string guid)
        {
            Cart cart = this.GetCartByGuid(guid);
            IList<Product> productsInUserCard = cart.CartItems.Where(ci => ci.IsDeleted == false).Select(ci=> ci.Product).ToList();
            return productsInUserCard;
        }
	}
}
